package org.dromara.common.tenant.helper

import cn.dev33.satoken.context.SaHolder
import cn.hutool.core.collection.CollectionUtil
import cn.hutool.core.convert.Convert
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper
import io.github.oshai.kotlinlogging.KotlinLogging
import org.dromara.common.core.constant.GlobalConstants
import org.dromara.common.core.utils.SpringUtils.getProperty
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.core.utils.reflect.ReflectUtils.getField
import org.dromara.common.core.utils.reflect.ReflectUtils.getStaticFieldValue
import org.dromara.common.redis.utils.RedisUtils.deleteObject
import org.dromara.common.redis.utils.RedisUtils.getCacheObject
import org.dromara.common.redis.utils.RedisUtils.setCacheObject
import org.dromara.common.satoken.utils.LoginHelper
import org.dromara.common.satoken.utils.LoginHelper.getUserId
import org.dromara.common.satoken.utils.LoginHelper.isLogin
import org.dromara.common.tenant.helper.TenantHelper.disableIgnore
import java.util.*
import java.util.function.Supplier

/**
 * 租户助手
 *
 * @author LikeYouDo
 * @date 2025/07/26
 */
object TenantHelper {
    private val log = KotlinLogging.logger { }
    private const val DYNAMIC_TENANT_KEY: String = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"
    private val TEMP_DYNAMIC_TENANT: ThreadLocal<String> = ThreadLocal<String>()
    private val REENTRANT_IGNORE: ThreadLocal<Stack<Int?>> = ThreadLocal.withInitial { Stack() }

    /**
     * 租户功能是否启用
     */
    @JvmStatic
    fun isEnable(): Boolean {
        return Convert.toBool(getProperty("tenant.enable"), false);
    }

    private fun getIgnoreStrategy(): IgnoreStrategy? {
        val ignoreStrategyLocal =
            getStaticFieldValue(getField(InterceptorIgnoreHelper::class.java, "IGNORE_STRATEGY_LOCAL"))
        if (ignoreStrategyLocal is ThreadLocal<*>) {
            val ignoreStrategy = ignoreStrategyLocal.get()
            if (ignoreStrategy is IgnoreStrategy) {
                return ignoreStrategy
            }
        }
        return null
    }

    /**
     * 开启忽略租户(开启后需手动调用 [disableIgnore] 关闭)
     */
    @JvmStatic
    fun enableIgnore() {
        val ignoreStrategy: IgnoreStrategy? = getIgnoreStrategy()
        if (ignoreStrategy == null) {
            InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build())
        } else {
            ignoreStrategy.tenantLine = true
        }
        val reentrantStack = REENTRANT_IGNORE.get()
        reentrantStack.push(reentrantStack.size + 1)
    }

    /**
     * 关闭忽略租户
     */
    @JvmStatic
    fun disableIgnore() {
        val ignoreStrategy: IgnoreStrategy? = getIgnoreStrategy()
        if (ignoreStrategy != null) {
            val noOtherIgnoreStrategy = true != ignoreStrategy.dynamicTableName
                && true != ignoreStrategy.blockAttack
                && true != ignoreStrategy.illegalSql
                && true != ignoreStrategy.dataPermission
                && CollectionUtil.isEmpty(ignoreStrategy.others)
            val reentrantStack = REENTRANT_IGNORE.get()
            val empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1
            if (noOtherIgnoreStrategy && empty) {
                InterceptorIgnoreHelper.clearIgnoreStrategy()
            } else if (empty) {
                ignoreStrategy.tenantLine = false
            }
        }
    }

    /**
     * 在忽略租户中执行
     *
     * @param handle 处理执行方法
     */
    @JvmStatic
    fun ignore(handle: Runnable) {
        enableIgnore()
        try {
            handle.run()
        } finally {
            disableIgnore()
        }
    }

    /**
     * 在忽略租户中执行
     *
     * @param handle 处理执行方法
     */
    @JvmStatic
    fun <T> ignore(handle: Supplier<T>): T {
        enableIgnore()
        try {
            return handle.get()
        } finally {
            disableIgnore()
        }
    }

    @JvmStatic
    fun setDynamic(tenantId: String?) {
        setDynamic(tenantId, false)
    }

    /**
     * 设置动态租户(一直有效 需要手动清理)
     *
     *
     * 如果为未登录状态下 那么只在当前线程内生效
     *
     * @param tenantId 租户id
     * @param global   是否全局生效
     */
    @JvmStatic
    fun setDynamic(tenantId: String?, global: Boolean) {
        if (!isEnable()) {
            return
        }
        if (!isLogin() || !global) {
            TEMP_DYNAMIC_TENANT.set(tenantId)
            return
        }
        val cacheKey = DYNAMIC_TENANT_KEY + ":" + getUserId()
        setCacheObject(cacheKey, tenantId)
        SaHolder.getStorage().set(cacheKey, tenantId)
    }

    /**
     * 获取动态租户(一直有效 需要手动清理)
     *
     *
     * 如果为未登录状态下 那么只在当前线程内生效
     */
    @JvmStatic
    fun getDynamic(): String? {
        if (!isEnable()) {
            return null
        }
        if (!isLogin()) {
            return TEMP_DYNAMIC_TENANT.get()
        }
        // 如果线程内有值 优先返回
        var tenantId = TEMP_DYNAMIC_TENANT.get()
        if (StringUtils.isNotBlank(tenantId)) {
            return tenantId
        }
        val storage = SaHolder.getStorage()
        val cacheKey = DYNAMIC_TENANT_KEY + ":" + getUserId()
        tenantId = storage.getString(cacheKey)
        // 如果为 -1 说明已经查过redis并且不存在值 则直接返回null
        if (StringUtils.isNotBlank(tenantId)) {
            return if (tenantId == "-1") null else tenantId
        }
        tenantId = getCacheObject<String>(cacheKey)
        storage.set(cacheKey, if (StringUtils.isBlank(tenantId)) "-1" else tenantId)
        return tenantId
    }

    /**
     * 清除动态租户
     */
    @JvmStatic
    fun clearDynamic() {
        if (!isEnable()) {
            return
        }
        if (!isLogin()) {
            TEMP_DYNAMIC_TENANT.remove()
            return
        }
        TEMP_DYNAMIC_TENANT.remove()
        val cacheKey = DYNAMIC_TENANT_KEY + ":" + getUserId()
        deleteObject(cacheKey)
        SaHolder.getStorage().delete(cacheKey)
    }

    /**
     * 在动态租户中执行
     *
     * @param handle 处理执行方法
     */
    @JvmStatic
    fun dynamic(tenantId: String?, handle: Runnable) {
        setDynamic(tenantId)
        try {
            handle.run()
        } finally {
            clearDynamic()
        }
    }

    /**
     * 在动态租户中执行
     *
     * @param handle 处理执行方法
     */
    @JvmStatic
    fun <T> dynamic(tenantId: String?, handle: Supplier<T>): T {
        setDynamic(tenantId)
        try {
            return handle.get()
        } finally {
            clearDynamic()
        }
    }

    /**
     * 获取当前租户id(动态租户优先)
     */
    @JvmStatic
    fun getTenantId(): String? {
        if (!isEnable()) {
            return null
        }
        var tenantId = getDynamic()
        if (StringUtils.isBlank(tenantId)) {
            tenantId = LoginHelper.getTenantId()
        }
        return tenantId
    }

}
